#include <kernel/kobject.h>
#include <kernel/initcall.h>
#include <kernel/syslog.h>
#include <mm/mem.h>
#include <drivers/platform.h>
#include <drivers/spi.h>
#include <stm32f4.h>
#include <kernel/fdt.h>
#include <drivers/clock.h>
#include <io.h>


#define SPI_CR1_OFFSET                 (0x00)
#define SPI_CR2_OFFSET                 (0x04)
#define SPI_SR_OFFSET                  (0x08)
#define SPI_DR_OFFSET                  (0x0C)
#define SPI_CRCPR_OFFSET               (0x10)
#define SPI_RXCRCR_OFFSET              (0x14)
#define SPI_TXCRCR_OFFSET              (0x18)
#define SPI_I2S_CFGR_OFFSET            (0x1C)
#define SPI_I2SPR_OFFSET               (0x20)

#define SPI_CR1_ENABLE_BIT             (6) // SPI enable bit


static int stm32f4_spi_calculate_freq_div(struct spi_device *spi)
{
    size_t freq_hz = spi->master->clock_freq_hz;
    size_t freq_div = 0x7;
    uint32_t cr1 = 0;
    uint32_t div_tab[8] = {2, 4, 8, 16, 32, 64, 128, 256};

    freq_div = freq_hz / spi->speed;

    if(freq_div < 2) {
        SPI_SYSLOG_WARN("SPI speed too high, set to max: %d Hz", freq_hz >> 1);
        return 0x7; //max speed
    }

    if(freq_div > 256) {
        SPI_SYSLOG_WARN("SPI speed too low, set to min: %d Hz", freq_hz >> 9);
        return 0; //min speed
    }

    for(int i = 0; i < sizeof(div_tab) - 1; i++) {
        if(freq_div == div_tab[i]) {
            freq_div = i;
            SPI_SYSLOG_INFO("SPI speed set to %d Hz", freq_hz >> (i + 1));
            return freq_div;
        }
        else if(freq_div > div_tab[i] && freq_div <= div_tab[i + 1]) {
            freq_div = i + 1;
            SPI_SYSLOG_INFO("SPI speed set to %d Hz", freq_hz >> (i + 2));
            return freq_div;
        }
    }

    return 0x7; //max speed
}


static void stm32f4_spi_setup(struct spi_device *spi)
{
    uint32_t spi_base = (uint32_t)spi->master->io_base;
    uint32_t cr1 = 0;
    int div = stm32f4_spi_calculate_freq_div(spi);

    if(spi->bits == 8) {
        cr1 |= 0 << 11; //使用8位数据帧格式进行发送/接收；
    }
    else if(spi->bits == 16){
        cr1 |= 1 << 11; //使用16位数据帧格式进行发送/接收；
    }

    if(spi->duplex) {
        cr1 |= 1 << 10; //half-duplex mode
    }
    else {
        cr1 |= 0 << 10; //full-duplex mode
    }

    cr1 |= 1<<9;        //开启CS引脚管理
    cr1 |= 1<<8;        //NSS，用于设置CS引脚电平

    if(spi->lsb) {
        cr1 |= 1<<7;    //lsb mode, first send low bit
    }
    else {
        cr1 |= 0<<7;    //msb mode, first send high bit
    }

    cr1 |= div << 3;    //当总线频率为36MHZ时，SPI速度为18MHZ，高速。
    cr1 |= 1<<2;        //配置为主设备

    if(spi->mode & SPI_MODE_CPOL_MASK) {
        cr1 |= 1<<1;    //空闲状态时， SCK保持高电平。 CPOL=1 
    }
    
    if(spi->mode & SPI_MODE_CPHA_MASK) {
        cr1 |= 1<<0;    //数据采样从第二个时钟边沿开始。CPHA=1  
    }

    write32(spi_base + SPI_CR1_OFFSET, cr1);                 //设置SPI控制寄存器1
    cr1 |= BIT(SPI_CR1_ENABLE_BIT);                    //使能SPI外设
    write32(spi_base + SPI_CR1_OFFSET, cr1); 
}


static void stm32f4_spi_set_cs(struct spi_device *spi, bool enable)
{
    spi_gpio_set_cs(spi, enable);
}


static int stm32f4_spi_transfer(struct spi_device *spi, struct spi_transfer *xfer)
{
    uint32_t spi_base = (uint32_t)spi->master->io_base;
    uint8_t *tx_buf = (uint8_t*)xfer->tx_buf;
    uint8_t *rx_buf = (uint8_t*)xfer->rx_buf;
    uint32_t len = xfer->len;

    stm32f4_spi_set_cs(spi, true);

    for(int i = 0; i < len; i++) {
        while(! (read32(spi_base + SPI_SR_OFFSET) & BIT(1)));   //wait for send buffer empty
        if(tx_buf != NULL) {
            write32(spi_base + SPI_DR_OFFSET, *tx_buf);   //send a byte
            tx_buf ++;
        }

        while(! (read32(spi_base + SPI_SR_OFFSET) & BIT(0)));   //wait for receive buffer not empty
        if(rx_buf != NULL) {
            *rx_buf = read32(spi_base + SPI_DR_OFFSET);   //receive a byte
            rx_buf ++;
        }
    }

    while(read32(spi_base + SPI_SR_OFFSET) & BIT(7));   //wait for send finished
    stm32f4_spi_set_cs(spi, false);
}


static int stm32f4_spi_driver_probe(struct platform_device *dev)
{
    fdt_node_t *spi_node= dev->device.dt_node;
    struct spi_gpio gpio_pin;

    const char *spi_clock = fdt_read_prop_string(spi_node, "clock-bus");
    if(spi_clock == NULL) {
        return -1;
    }

    size_t clock_bit = -1;
    if(fdt_read_prop_int(spi_node, "clock-bit", &clock_bit)) {
        PLATFORM_SYSLOG_ERROR("spi: no clock-bit property");
        return -1;
    }

    clock_enable_by_name(spi_clock, clock_bit);

    if(spi_master_get_gpio_from_fdt(spi_node, &gpio_pin)) {
        return -1;
    }
    
    size_t irq_num = 0;
    if(fdt_read_prop_int(spi_node, "interrupts", &irq_num)) {
        PLATFORM_SYSLOG_INFO("spi: no interrupts property");
    }
    
    struct spi_master *master = kmalloc(sizeof(struct spi_master));
    if(master == NULL) {
        PLATFORM_SYSLOG_ERROR("spi: kmalloc failed");
        return -1;
    }

    master->io_base = dev->io_base;
    master->set_cs = stm32f4_spi_set_cs;
    master->setup = stm32f4_spi_setup;
    master->transfer = stm32f4_spi_transfer;
    master->pin = gpio_pin;
    master->clock_freq_hz = clock_get_freq_by_name(spi_clock);

    platform_set_drvdata(dev, master);

    fdt_node_t *child =  NULL;
    fdt_for_each_node_child(spi_node, child) {

        struct spi_device *spi_dev = kmalloc(sizeof(struct spi_device));
        if(spi_dev == NULL) {
            PLATFORM_SYSLOG_ERROR("spi: alloc spi device failed");
            return -1;
        }

        if(spi_device_get_config_from_fdt(child, spi_dev)) {
            kfree(spi_dev);
            return -1;
        }

        if(spi_device_register(spi_dev)) {
            PLATFORM_SYSLOG_ERROR("spi: spi device register failed");
            return -1;
        }

        spi_dev->master = master;

        if(irq_num) {
            spi_dev->irq_num = irq_num;
        }
    }

    return 0;
}


static void stm32f4_spi_driver_remove(struct platform_device *dev)
{

}


static struct of_device_id stm32f4_spi_driver_match[] = {
    {.compatible = "stm32f4-spi", },
    { }
};


static struct platform_driver stm32f4_spi_pdrv = {
    .driver = {
        .name = "stm32f4-spi",
        .of_match_table = stm32f4_spi_driver_match,
    },
    .probe = stm32f4_spi_driver_probe,
    .remove = stm32f4_spi_driver_remove,
};


static int stm32f4_spi_driver_register(void)
{
    return platform_driver_register(&stm32f4_spi_pdrv);
}


driver_initcall(stm32f4_spi_driver_register);
